package net.sinodata.smartframework.exception.handler;

import net.sinodata.smartframework.common.CommonResult;
import net.sinodata.smartframework.common.ResultCode;
import net.sinodata.smartframework.exception.BaseException;
import net.sinodata.smartframework.exception.BusinessException;
import net.sinodata.smartframework.exception.ParamCheckException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.Objects;

/**
 * Description: 全局异常 跳转 /error 控制
 * 
 * @author: yinliangyun
 * @date: 2021-5-18
 * 
 * ControllerAdvice作用：
 * ExceptionHandler作用：自动捕获controller层出现的指定类型异常,并对该异常进行相应的异常处理.
 *
 * 异常处理可以分为三种。
 *
 * 1、第一种是进入@Controller标识的方法前 产生的异常，例如URL地址错误。这种异常处理需要 异常处理类通过实现 ErrorController 来处理。
 *
 * 2、第二种是进入Controller时，但还未进行逻辑处理时 产生的异常，例如传入参数数据类型错误。
 * 这种异常处理需要用@ControllerAdvice标识并处理，建议继承ResponseEntityExceptionHandler 来处理，
 * 该父类包括了很多已经被@ExceptionHandler 注解标识的方法，包括一些参数转换，请求方法不支持等类型等等。
 *
 * 3、第三种是进入Controller，进行逻辑处理时产生的异常，例如NullPointerException异常等。
 * 这种异常处理也可以用@ControllerAdvice来标识并进行处理，也建议继承ResponseEntityExceptionHandler 处理，
 * 这里我们可以用@ExceptionHandler 自定义捕获的异常并处理。
 *
 * 以上三种情况都是restful的情况，结果会返回一个Json。
 *
 * 如果希望返回跳转页面，则需要实现HandlerExceptionResolver类来进行异常处理并跳转。
 *
 * 意@ControllerAdvice需要搭配@ExceptionHandler来使用，自定义捕获并处理异常。
 */
@ControllerAdvice("net.sinodata.smartframework")
@ResponseBody
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

	private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

	/**
	 * 400错误 参数不合法
	 */
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
		writeExceptionLogFile(e);
		StringBuilder message = new StringBuilder();
		for (final FieldError error : e.getBindingResult().getFieldErrors()) {
			message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append(" ");
		}

		for (final ObjectError error : e.getBindingResult().getGlobalErrors()) {
			message.append(error.getObjectName()).append(": ").append(error.getDefaultMessage()).append(" ");
		}

		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), null == message ? "null" : message.toString());
		return handleExceptionInternal(e, result, headers, HttpStatus.OK, request);
	}

	/**
	 * 400 参数绑定失败
	 */
	@Override
	protected ResponseEntity<Object> handleBindException(final BindException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		StringBuilder message = new StringBuilder();
		for (final FieldError error : e.getBindingResult().getFieldErrors()) {
			message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append(" ");
		}

		for (final ObjectError error : e.getBindingResult().getGlobalErrors()) {
			message.append(error.getObjectName()).append(": ").append(error.getDefaultMessage()).append(" ");
		}

		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), null == message ? "null" : message.toString());
		return handleExceptionInternal(e, result, headers, HttpStatus.OK, request);
	}

	/**
	 * 400  处理参数类型转换失败
	 */
	@Override
	protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		final String message = e.getValue() + " value for " + e.getPropertyName() + " should be of type " + e.getRequiredType();
		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), message);
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 400 缺少请求部分
	 */
	@Override
	protected ResponseEntity<Object> handleMissingServletRequestPart(final MissingServletRequestPartException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		final String message = e.getRequestPartName() + " part is missing";
		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), message);
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 400 缺少请求参数
	 */
	@Override
	protected ResponseEntity<Object> handleMissingServletRequestParameter(final MissingServletRequestParameterException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		final String message = e.getParameterName() + " parameter is missing";
		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), message);
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 400 自定义参数检查异常
	 */
	@ExceptionHandler({ParamCheckException.class})
	public ResponseEntity<Object> handleParamCheckException(final ParamCheckException e, final WebRequest request) {
		writeExceptionLogFile(e);
		StringBuilder message = new StringBuilder();
		message.append(e.getCauseInfo());
		String paramName = e.getParamName();
		if (StringUtils.hasLength(paramName)) {
			message.append(": ");
			message.append(paramName);
		}
		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), message.toString());
		logger.error(message.toString());
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 400 方法参数类型不匹配
	 */
	@ExceptionHandler({MethodArgumentTypeMismatchException.class})
	public ResponseEntity<Object> handleMethodArgumentTypeMismatch(final MethodArgumentTypeMismatchException e, final WebRequest request) {
		writeExceptionLogFile(e);
		final String message = e.getName() + " should be of type " + Objects.requireNonNull(e.getRequiredType()).getName();
		CommonResult<Object> result = CommonResult.failed(HttpStatus.BAD_REQUEST.value(), message);
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 404 handler未找到异常
	 */
	@Override
	protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(ex);
		final String message = "No handler found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
		CommonResult<Object> result = CommonResult.failed(HttpStatus.NOT_FOUND.value(), message);
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 405 请求方法不支持
	 */
	@Override
	protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(final HttpRequestMethodNotSupportedException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		final StringBuilder message = new StringBuilder();
		message.append(e.getMethod());
		message.append(" method is not supported for this request. Supported methods are ");
		Objects.requireNonNull(e.getSupportedHttpMethods()).forEach(t -> message.append(t).append(" "));
		CommonResult<Object> result = CommonResult.failed(HttpStatus.METHOD_NOT_ALLOWED.value(), message.toString());
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}

	/**
	 * 415 不支持的媒体类型
	 */
	@Override
	protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(final HttpMediaTypeNotSupportedException e, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
		writeExceptionLogFile(e);
		final StringBuilder message = new StringBuilder();
		message.append(e.getContentType());
		message.append(" media type is not supported. Supported media types are ");
		e.getSupportedMediaTypes().forEach(t -> message.append(t).append(" "));
		CommonResult<Object> result = CommonResult.failed(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), message.toString());
		return new ResponseEntity<>(result, new HttpHeaders(), HttpStatus.OK);
	}
	
	/**
	 * 捕获基础异常并处理
	 * 
	 * @param e
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	@ExceptionHandler(BaseException.class)
	public CommonResult baseExceptionHandler(BaseException e) {
		writeExceptionLogFile(e);
		return CommonResult.failed(e.getMessage());
	}

	/**
	 * 处理业务逻辑异常 ExceptionHandler这个注解是指当controller中抛出这个指定的异常类的时候，都会转到这个方法中来处理异常
	 */
	@SuppressWarnings("rawtypes")
	@ExceptionHandler(BusinessException.class)
	public CommonResult businessExceptionHandler(BusinessException e) {
		writeExceptionLogFile(e);
		return CommonResult.failed(e.getCode(), e.getMessage(), e.getData());
	}

	/**
	 * 500 其他服务器异常
	 */
	@SuppressWarnings("rawtypes")
	@ExceptionHandler(Exception.class)
	public CommonResult otherExceptionHandler(Exception e) {
		writeExceptionLogFile(e);
		return CommonResult.failed(ResultCode.OTHER.getCode(), ResultCode.OTHER.getMessage());
	}

	/**
	 * 异常信息记录到日志文件中
	 * 
	 * @param e
	 */
	private void writeExceptionLogFile(Exception e) {
		logger.error(e.getMessage(), e);
	}

}
